# Copyright (c) 2005,2006,2007 Dynamic Solutions Inc. (support@dynamic-solutions.com)
#
# This file is part of DataZoomer.
#
# DataZoomer is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# DataZoomer is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.


"""

DataZoomer Zapp class

All DataZoomer applications have to do basically one thing.  They have to
respond to a run() command and return a Response object.

It's this module's job to decide what code within the application is 
called and what response is sent back.

When called to run() the Zapp class decides which python module will be
loaded and which method will be called on the resulting class.

The method called will either return a value or not.  If not, then the
Zapp class will look for a .txt file to be rendered with the same name as the
controller.  If there is no .txt file then a blank page is returned.

If the method called does return a value, if it's a Response object it is
returned.  If it is a string it is wrapped in an HTMLResponse object and
returned.  If it's an iterable it assumed to be an array of HTML
data so it's joined and wrapped in an HTMLResponse object.

"""

from zoom import zoomer
import os
import dzresponse, dzutil, dzpage, dzml, dzfill

class Zapp:
    def __init__(self,path,name):
        self.name = name
        product = zoomer.db('select * from dz_products where name=%s', self.name)
        if len(product)>0:
           product = product[0]
           self.subtitle    = product.SUBTITLE
           self.iconname    = product.ICONNAME
           self.version     = product.VERSION
           self.userid      = product.USERID
           self.system_app  = product.SYSTEM
           self.position    = product.POSITION
           self.visible     = product.VISIBLE
           self.title       = product.TITLE
           self.status      = product.STATUS
        else:
           self.subtitle    = ''
           self.iconname    = 'blank_doc'
           self.version     = '0.1'
           self.userid      = ''
           self.system_app  = 0
           self.position    = 1
           self.visible     = 1
           self.title       = self.name.capitalize()
           self.status      = 'U'

        self.url = dzutil.url_for(app=self.name)
        self.controller_name = zoomer.webvars.con or None
        self.action_name = zoomer.webvars.action or None
        self.content = 'Hello World!'
        self.controllers = []
        self.actions = []
        self.path = path

    def run(self):
        if not self.before_dispatch():
            t = self.dispatch()
            self.after_dispatch()
        return self.respond(t)

    def respond(self,t):
        zoomer.debug('preparing response')
        if t:
            if hasattr(t,'render') and t.render:  # have second test here in case t is an Entity, which returns None for missing attributes rather than exceptions ( todo: change to exception )
                zoomer.debug('controller returned instance of renderable %s' % t.__class__.__name__)
                t2 = t.render()
                if hasattr(t2,'render'):
                    result = t2
                else:
                    result = t
                return result

            elif type(t) == type([]):  # an array of string-like things (like WSGI)
                zoomer.debug('controller returned array')
                return dzresponse.HTMLResponse(''.join(t))

            elif type(t) == type(""):  # a string-like thing
                zoomer.debug('controller returned string')
                return dzresponse.HTMLResponse(t)

        zoomer.debug('controller returned None or unknown response')
        return self.render_view()

    def load_module(self,pathname):
        if os.path.isfile(pathname):
            namespace = {'zoomer':zoomer, 'app':self}
            execfile(pathname,namespace)
            return namespace.get('result',None)

    def load_app(self,pathname):
        if os.path.isfile(pathname):
            namespace = {'zoomer':zoomer, 'app':self}
            execfile(pathname,namespace)
            return namespace.get('result',None)

    def load_controller(self):
        """Decide which python program is the controller and load it"""
        if self.controller_name:
            controller_name = '%s_controller.py' % self.controller_name
        else:
            controller_name = 'controller.py'
#        zoomer.debug('controller_name %s'%controller_name)
        pathname = os.path.join(self.path,'controllers',controller_name)
        if not os.path.isfile(pathname):
            if os.path.isfile(os.path.join(self.path,'controllers','index.py')):
                pathname = os.path.join(self.path,'controllers','index.py')
            elif os.path.isfile(os.path.join(self.path,'controllers','index_controller.py')):
                pathname = os.path.join(self.path,'controllers','index_controller.py')
#        zoomer.debug('no controller')
        result = self.load_module(pathname)
        if result:
            zoomer.debug('controller: %s in %s' % (result.__class__,pathname))
        else:
            zoomer.debug('no controller')
        return result

    def dispatch(self):
        def call(controller,action,p):
            if action:
                handler_name = '_%s'%action
                handler = hasattr(controller,handler_name) and getattr(controller,handler_name) or None
                if handler:
                    try:
                        result = handler(**p)
                    except:
                        zoomer.debug('paramters passed to handler: %s' % p)
                        raise
                    if result: return result

        """Yet another app structure"""
        pathname = os.path.join(self.path,'app.py')
        if os.path.isfile(pathname):
            import imp
            app = getattr(imp.load_source('app',pathname),'app')
            if app:
                return app()

        """Application Initialization Module"""
        self.load_app(os.path.join(self.path,'zapp.py'))

        """Load the controller to handle the request"""
        controller = self.load_controller()

        if controller:
            """Gather parameters together in preparation for calling methods"""
            p = {}
            button_action = None
            for key in zoomer.webvars.__dict__:
                if key not in ['sid','app','con','action']:
                    if key.split('_',1)[-1:][0]=='button':
                        """Only one button action should be able to happen in single a form submission"""
                        button_action = key
                    else:
                        p[key] = zoomer.webvars.__dict__[key]

            """If a button has been pressed call the appropriate method"""
            return \
                call(controller,button_action,p) or \
                call(controller,self.action_name,p) or \
                call(controller,'index',p)



    def before_dispatch(self):
        pass

    def after_dispatch(self):
        pass

#    def render(self,*args,**keywords):
    def render_view(self):
        def param_list(**kv):
            p = {}
            for k in kv:
                if kv[k]:
                    p[k] = kv[k]
            return p

        def handle(*args,**kv):
            if args[0]=='action':
                p = param_list(con=self.controller_name,action=args[1],key=zoomer.webvars.key)
                page.actions.append(dzutil.link_to(args[2],**p))

        def load_template(filename):
            f = open(filename,'rb')
            t = f.read()
            f.close()
            return t

        def load_view(template_name):
            path,ext = template_name.rsplit('.',1)
            pathname = path+'.py'
            return self.load_module(pathname)

        controller = self.controller_name
        action = self.action_name
        if controller and action and os.path.exists('views/%s_%s.txt' % (controller,action)):
            template_name = 'views/%s_%s.txt' % (controller,action)
        elif controller and os.path.exists('views/%s_index.txt' % (controller)):
            template_name = 'views/%s_index.txt' % (controller)
        elif action and os.path.exists('views/%s.txt' % (action)):
            template_name = 'views/%s.txt' % (action)
        else:
            template_name = 'views/index.txt'

        if os.path.exists(template_name):
            tpl  = load_template(template_name)
            view = load_view(template_name)
            zoomer.debug('template: %s' % (template_name))

            page = dzpage.Page()
            if action==None:
                action='accept'
#            t = dzml.dzml(tpl,callback=handle,form=1,action=action)
            if view:
                zoomer.debug('view: %s' % (view.__class__))
                t = dzml.dzml(tpl,callback=handle,form=1,action=action,view=view)
                t = dzfill.fill(t,view)
            else:
                t = dzml.dzml(tpl,callback=handle,form=1,action=action)
                zoomer.debug('no view module')
                # could put a custom renderer in here
            page.content = t
            return page.render()

        # May want to allow for rendering views that have no template but just a .py file and a render() method

        #if template is missing then look for legacy template
        legacy_template_name = 'views/index.txt'
        controller = self.controller_name or 'index'
        action     = self.action_name or 'index'
        if action == 'index':
            legacy_template_name = 'views/%s.txt' % controller
        else:
            legacy_template_name = 'views/%s_%s.txt' % (controller,action)

        if os.path.exists(legacy_template_name):
            template_name = legacy_template_name

        if os.path.exists(template_name):
            content = dzml.dzml(dzutil.include(template_name),folder='views',callback=handle)
        else:
            content = 'Unknown template %s' % template_name

        page = dzpage.Page()
        page.content = content
        return page.render()




